package com.example.springboot.search.util;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0Util {

	private static Log log = LogFactory.getLog(C3P0Util.class);

	private static ThreadLocal<Connection> connect = new ThreadLocal<Connection>();

	private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

	/**
	 * 在构造函数初始化的时候获取数据库连接
	 */
	static {
		try {
			log.info("开始加载数据库连接配置文件........");
			/** 通过属性文件获取数据库连接的参数值 **/
			Properties properties = new Properties();
			InputStream stream = C3P0Util.class.getClassLoader().getResourceAsStream("config.properties");
			properties.load(stream);
			/** 获取属性文件中的值 **/
			String driverClassName = properties.getProperty("jdbc.driverClassName");
			String url = properties.getProperty("jdbc.url");
			String username = properties.getProperty("jdbc.username");
			String password = properties.getProperty("jdbc.password");
			String InitialPoolSize = properties.getProperty("jdbc.InitialPoolSize");
			String MaxPoolSize = properties.getProperty("jdbc.MaxPoolSize");
			String AcquireIncrement = properties.getProperty("jdbc.AcquireIncrement");
			String IdleConnectionTestPeriod = properties.getProperty("jdbc.IdleConnectionTestPeriod");
			String MaxIdleTime = properties.getProperty("jdbc.MaxIdleTime");
			String AcquireRetryAttempts = properties.getProperty("jdbc.AcquireRetryAttempts");
			String AcquireRetryDelay = properties.getProperty("jdbc.AcquireRetryDelay");

			/** 设置数据库连接驱动 **/
			dataSource.setDriverClass(driverClassName);
			/** 设置数据库连接地址 **/
			dataSource.setJdbcUrl(url);
			/** 设置数据库连接用户名 **/
			dataSource.setUser(username);
			/** 设置数据库连接密码 **/
			dataSource.setPassword(password);

			/** 初始化时创建的连接数,应在minPoolSize与maxPoolSize之间取值.默认为3 **/
			dataSource.setInitialPoolSize(10);
			/** 连接池中保留的最大连接数据.默认为15 **/
			dataSource.setMaxPoolSize(30);
			/** 当连接池中的连接用完时，C3PO一次性创建新的连接数目; **/
			dataSource.setAcquireIncrement(10);
			/** 隔多少秒检查所有连接池中的空闲连接,默认为0表示不检查; **/
			dataSource.setIdleConnectionTestPeriod(60);
			/** 最大空闲时间,超过空闲时间的连接将被丢弃.为0或负数据则永不丢弃.默认为0; **/
			dataSource.setMaxIdleTime(3000);

			/**
			 * 因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的
			 * 时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable
			 * 等方法来提升连接测试的性能。Default: false
			 **/
			dataSource.setTestConnectionOnCheckout(true);

			/** 如果设为true那么在取得连接的同时将校验连接的有效性。Default: false **/
			dataSource.setTestConnectionOnCheckin(true);
			/** 定义在从数据库获取新的连接失败后重复尝试获取的次数，默认为30; **/
			dataSource.setAcquireRetryAttempts(30);
			/** 两次连接中间隔时间默认为1000毫秒 **/
			dataSource.setAcquireRetryDelay(1000);
			/**
			 * 获取连接失败将会引起所有等待获取连接的线程异常,
			 * 但是数据源仍有效的保留,并在下次调用getConnection()的时候继续尝试获取连接.如果设为true,
			 * 那么尝试获取连接失败后该数据源将申明已经断开并永久关闭.默认为false
			 **/
			dataSource.setBreakAfterAcquireFailure(true);

			log.info("初始化数据库连接完成........");
		} catch (Exception e) {
			e.printStackTrace();
			log.error("加载数据库连接配置文件异常：" + e);
		}
	}

	/**
	 * 获取数据库连接
	 * 
	 * @return 数据库连接
	 */
	public static Connection getConnection() throws SQLException {
		Connection connection = connect.get();
		if (connection == null) {
			synchronized (C3P0Util.class) {
				if (connection == null) {
					connection = dataSource.getConnection();
				}
			}
		}
		return connection;
	}

	/**
	 * 开启事务
	 * 
	 * @throws SQLException
	 */
	public static void begin() throws SQLException {
		Connection con = connect.get();// 获取当前线程的事务连接
		if (con != null)
			throw new SQLException("已经开启了事务，不能重复开启！");
		con = dataSource.getConnection();// 给con赋值，表示开启了事务
		con.setAutoCommit(false);// 设置为手动提交
		connect.set(con);// 把当前事务连接放到connect中
	}

	/**
	 * 提交事务
	 * 
	 * @throws SQLException
	 */
	public static void commit() throws SQLException {
		Connection con = connect.get();// 获取当前线程的事务连接
		if (con == null) {
			throw new SQLException("没有事务不能提交！");
		}
		con.commit();// 提交事务
		con.close();// 关闭连接
		con = null;// 表示事务结束！
		connect.remove();
	}

	/**
	 * 回滚事务
	 * 
	 * @throws SQLException
	 */
	public static void rollback() throws SQLException {
		Connection con = connect.get();// 获取当前线程的事务连接
		if (con == null) {
			throw new SQLException("没有事务不能回滚！");
		}
		con.rollback();
		con.close();
		con = null;
		connect.remove();
	}

	/**
	 * 关闭连接
	 * 
	 * @throws SQLException
	 */
	public static void close(Connection con) throws SQLException {
		if (con != null) {
			con.close();
			con = null;
		}
	}

	/**
	 * 释放Connection
	 * 
	 * @throws SQLException
	 */
	public static void release(Connection connection) throws SQLException {
		Connection con = connect.get();// 获取当前线程的事务连接
		if (connection != con) {// 如果参数连接，与当前事务连接不同，说明这个连接不是当前事务，可以关闭！
			if (connection != null && !connection.isClosed()) {// 如果参数连接没有关闭，关闭之！
				connection.close();
			}
		}
	}

	public static QueryRunner getQueryRuner() {
		return new QueryRunner(dataSource);
	}

	/*
	 * 删除
	 */
	public static Integer delete(String sql, Object... params) throws SQLException {
		log.info("delete删除数据sql: " + sql);
		log.info("delete删除数据参数: " + Arrays.toString(params));
		Integer res = C3P0Util.getQueryRuner().update(sql, params);
		log.info("delete删除数据结果: " + res);
		return res;
	}

	/*
	 * 修改
	 */
	public static Integer update(String sql, Object... params) throws SQLException {
		log.info("update修改数据sql: " + sql);
		log.info("update修改数据参数: " + Arrays.toString(params));
		Integer res = C3P0Util.getQueryRuner().update(sql, params);
		log.info("update修改数据结果: " + res);
		return res;
	}

	/*
	 * 新增
	 */
	public static Integer insert(String sql, Object... params) throws SQLException {
		log.info("insert新增数据sql: " + sql);
		log.info("insert新增数据参数: " + Arrays.toString(params));
		Integer res = C3P0Util.getQueryRuner().insert(sql, new ScalarHandler<Integer>(), params);
		log.info("insert新增数据结果: " + res);
		return res;
	}

	/*
	 * 批量新增、更新、删除操作， ——batch是批处理。所以需要的参数是一个数组，这个数组第一个参数代表执行的次数，第二个参数代表需要的参数个数
	 */
	public static int[] batch(String sql, Object[][]... params) throws SQLException {
		log.info("batch批量操作数据sql: " + sql);
		log.info("batch批量操作数据参数: " + Arrays.toString(params));
		int[] res = C3P0Util.getQueryRuner().batch(sql, params);
		log.info("batch批量操作数据结果: " + Arrays.toString(res));
		return res;
	}

	/**
	 * 结果集的第三种处理方法BeanHandler 将结果集的第一行数据，封装为JavaBean对象
	 * 注意：被封装成数据到JavaBean对象，Sort类必须有空参构造
	 * 
	 * @param <T>
	 * @throws SQLException
	 * */
	public static <T> T getBean(String sql, Class<T> clazz, Object... params) throws SQLException {
		log.info("getBean查询数据sql: " + sql);
		log.info("getBean查询数据参数: " + Arrays.toString(params));
		T bean = C3P0Util.getQueryRuner().query(sql, new BeanHandler<T>(clazz), params);
		log.info("getBean查询数据结果: " + bean);
		return bean;
	}

	/**
	 * 结果集第四种处理方法，BeanListHandler 将数据结果集的每一行数据，封装为JavaBean对象
	 * 多个JavaBean对象封装到List集合中
	 * 
	 * @throws SQLException
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 * */
	public static <T> List<T> getBeanList(String sql, Class<T> clazz, Object... params) throws Exception {
		log.info("getBeanList查询数据sql: " + sql);
		log.info("getBeanList查询数据参数: " + Arrays.toString(params));
		List<T> list = C3P0Util.getQueryRuner().query(sql, new BeanListHandler<T>(clazz), params);
		log.info("getBeanList查询数据结果: " + (list != null ? list.size() : 0));
		return list;
	}

	/**
	 * 结果集第五种处理方法，ColumnListHandler 结果集，指定列的数据，存储到List集合中 List<Object> 每个列数据类型不同
	 * 
	 * @throws SQLException
	 * */
	public static <T> List<T> getColumnList(String sql, String columName) throws SQLException {
		log.info("getColumnList查询数据sql: " + sql);
		log.info("getColumnList查询数据参数: " + columName);
		List<T> list = C3P0Util.getQueryRuner().query(sql, new ColumnListHandler<T>(columName));
		log.info("getColumnList查询数据结果: " + (list != null ? list.size() : 0));
		return list;

	}

	/**
	 * 结果集第六种处理方法、 对于查询后，只有一个结果，按照指定字段查询，用户统计等计算
	 * 
	 * @throws SQLException
	 * */
	public static <T> T getCount(String sql, Object... params) throws SQLException {
		log.info("getCount查询数据sql: " + sql);
		log.info("getCount查询数据参数: " + Arrays.toString(params));
		T t2 = C3P0Util.getQueryRuner().query(sql, new ScalarHandler<T>(), params);
		log.info("getCount查询数据结果: " + t2);
		return t2;
	}

	/**
	 * 结果集的第七种处理方式，MapHandler 将结果集第一行数据，封装到Map集合中 Map<键，值> 键：列名 值：这列数据
	 * 
	 * @throws SQLException
	 * */
	public static Map<String, Object> getMap(String sql, Object... params) throws SQLException {
		log.info("getMap查询数据sql: " + sql);
		log.info("getMap查询数据参数: " + Arrays.toString(params));
		Map<String, Object> map = C3P0Util.getQueryRuner().query(sql, new MapHandler(), params);
		log.info("getMap查询数据结果: " + (map != null ? map.size() : 0));
		return map;
	}

	/**
	 * 结果集第八种处理方法，MapListHandler 将结果集每一行存储到Map集合，键：列名 值：数据 Map集合过多，存储到List集合
	 * 
	 * @throws SQLException
	 * */
	public static List<Map<String, Object>> getMapList(String sql, Object... params) throws SQLException {
		log.info("getMapList查询数据sql: " + sql);
		log.info("getMapList查询数据参数: " + Arrays.toString(params));
		List<Map<String, Object>> list = C3P0Util.getQueryRuner().query(sql, new MapListHandler(), params);
		log.info("getMapList查询数据结果: " + (list != null ? list.size() : 0));
		return list;
	}
}